package query.update

import ast.expr.SqlIdentifierExpr
import ast.statement.update.SqlUpdate
import dsl.column
import expr.DB
import expr.Query
import expr.QueryTableColumn
import expr.TableSchema
import query.ReviseQuery
import util.toSqlString
import visitor.getExpr
import visitor.getQueryExpr
import java.sql.Connection
import java.sql.SQLException
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties

class Update(
    var db: DB = DB.MYSQL,
    override var conn: Connection? = null,
    override var isTransaction: Boolean = false
) : ReviseQuery() {
    constructor(db: DB) : this(db, null, false)

    private var sqlUpdate = SqlUpdate()

    infix fun update(table: String): Update {
        this.sqlUpdate.table = SqlIdentifierExpr(table)
        return this
    }

    infix fun update(table: TableSchema): Update {
        return update(table.tableName)
    }

    infix fun update(entity: Any): Update {
        val clazz = entity::class
        val fields = clazz.declaredMemberProperties
            .map { it.name to it.getter.call(entity) }
            .toMap()

        val companion = clazz.companionObjectInstance ?: throw Exception("实体类需要添加伴生对象")
        val companionClass = companion::class
        val table = companion as TableSchema
        update(table.tableName)

        val columns = companionClass.declaredMemberProperties
            .map { it.name to it.getter.call(companion) }
            .filter { it.second is QueryTableColumn }
            .map { it.first to (it.second as QueryTableColumn) }
            .toMap()

        val pkCols = columns
            .filter { it.value.primaryKey }
            .map { it.value.column to fields[it.key] }

        if (pkCols.isEmpty()) {
            throw SQLException("实体类的伴生对象中没有设置主键字段")
        }

        pkCols.forEach {
            if (it.second == null) {
                throw SQLException("主键为空")
            } else {
                where(column(it.first) eq it.second)
            }
        }

        val setCols = columns
            .filter { !it.value.primaryKey }
            .map { it.value.column to fields[it.key] }
            .filter { it.second != null }
            .map { column(it.first) to it.second!! }

        set(setCols)

        return this
    }

    infix fun set(item: Pair<Query, Any?>): Update {
        val (column, value) = item

        val valueExpr = if (value is Query) {
            getQueryExpr(value, this.db).expr
        } else {
            getExpr(value)
        }
        sqlUpdate.setList.add(getQueryExpr(column, this.db).expr to valueExpr)

        return this
    }

    infix fun set(items: List<Pair<Query, Any?>>): Update {
        items.forEach {
            val (column, value) = it

            val valueExpr = if (value is Query) {
                getQueryExpr(value, this.db).expr
            } else {
                getExpr(value)
            }
            sqlUpdate.setList.add(getQueryExpr(column, this.db).expr to valueExpr)
        }

        return this
    }

    fun set(vararg items: Pair<Query, Any?>): Update {
        items.forEach {
            val (column, value) = it

            val valueExpr = if (value is Query) {
                getQueryExpr(value, this.db).expr
            } else {
                getExpr(value)
            }
            sqlUpdate.setList.add(getQueryExpr(column, this.db).expr to valueExpr)
        }

        return this
    }

    fun incr(column: Query, value: Int): Update {
        sqlUpdate.setList.add(getQueryExpr(column, this.db).expr to getQueryExpr(column + value, this.db).expr)
        return this
    }

    infix fun incr(column: Query): Update {
        return incr(column, 1)
    }

    fun decr(column: Query, value: Int): Update {
        sqlUpdate.setList.add(getQueryExpr(column, this.db).expr to getQueryExpr(column - value, this.db).expr)
        return this
    }

    infix fun decr(column: Query): Update {
        return decr(column, 1)
    }

    infix fun where(condition: Query): Update {
        this.sqlUpdate.addCondition(getQueryExpr(condition, this.db).expr)
        return this
    }

    fun where(test: () -> Boolean, condition: Query): Update {
        if (test()) {
            where(condition)
        }

        return this
    }

    fun where(test: Boolean, condition: Query): Update {
        if (test) {
            where(condition)
        }

        return this
    }

    override fun sql() = toSqlString(sqlUpdate, db)
}